package com.ssd.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ssd.api.user.AddressApi;
import com.ssd.constants.RedisConstants;
import com.ssd.entity.Coupon;
import com.ssd.entity.CouponUseBack;
import com.ssd.entity.CouponWriteOff;
import com.ssd.entity.dto.request.CouponOperationPageQueryReqDTO;
import com.ssd.api.couponAndIntegral.dto.CouponUseBackReqDTO;
import com.ssd.api.couponAndIntegral.dto.CouponUseReqDTO;
import com.ssd.entity.dto.request.SeizeCouponReqDTO;
import com.ssd.entity.dto.response.ActivityInfoResDTO;
import com.ssd.api.couponAndIntegral.dto.AvailableCouponsResDTO;
import com.ssd.entity.dto.response.CouponInfoResDTO;
import com.ssd.api.couponAndIntegral.dto.CouponUseResDTO;
import com.ssd.enums.CouponStatusEnum;
import com.ssd.exception.LogicException;
import com.ssd.mapper.CouponMapper;
import com.ssd.service.IActivityService;
import com.ssd.service.ICouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ssd.service.ICouponUseBackService;
import com.ssd.service.ICouponWriteOffService;
import com.ssd.utils.CouponUtils;
import com.ssd.utils.DateUtils;
import com.ssd.utils.IdUtils;
import com.ssd.utils.UserContext;
import com.ssd.viewmodel.Pager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 我的优惠券表 服务实现类
 * </p>
 *
 * @author author
 * @since 2024-11-21
 */
@Service
@Slf4j
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private ICouponWriteOffService couponWriteOffService;
    @Resource
    private ICouponUseBackService couponUseBackService;
    @Resource(name = "seizeCouponScript")
    private DefaultRedisScript<Integer> seizeCouponScript;;
    @Resource
    private IActivityService activityService;
    @Resource
    private AddressApi addressApi;

    /**
     * 生成优惠券id 格式：{yyMMdd}{13位id}
     *
     * @return
     */
    private Long generatorCouponId() {
        //通过Redis自增序列得到序号
        Long id = redisTemplate.opsForValue().increment("coupon:page_key:generator", 1);
        long couponId = DateUtils.getFormatDate(LocalDateTime.now(), "yyMMdd") * 10000000000000L + id;
        return couponId;
    }

    /**
     * 根据活动id查询下面的优惠券列表
     *
     * @param couponOperationPageQueryReqDTO 查询参数
     * @return
     */
    @Override
    public Pager<CouponInfoResDTO> queryForPage(CouponOperationPageQueryReqDTO couponOperationPageQueryReqDTO) {
        //1.设置分页参数
        Page<Coupon> couponPage = new Page<>(couponOperationPageQueryReqDTO.getPageNum(), couponOperationPageQueryReqDTO.getPageSize());

        //3.执行查询并转换结果
        Page<Coupon> page = lambdaQuery()
                .eq(Coupon::getActivityId, couponOperationPageQueryReqDTO.getActivityId())
                .page(couponPage);
        if (page == null) {
            return Pager.buildEmpty();
        }
        //3.组装
        Page<CouponInfoResDTO> couponInfoResDTOPage = new Page<>();
        //分页数据
        couponInfoResDTOPage.setRecords(BeanUtil.copyToList(page.getRecords(), CouponInfoResDTO.class));
        //当前页码
        couponInfoResDTOPage.setCurrent(page.getCurrent());
        //每页条数
        couponInfoResDTOPage.setSize(page.getSize());
        //总数据数量
        couponInfoResDTOPage.setTotal(page.getTotal());
        return Pager.build(couponInfoResDTOPage);
    }

    @Override
    public List<CouponInfoResDTO> queryMyCouponForPage(Long lastId, Integer status) {
        //1.获取用户id
        Long userId = UserContext.currentUserId();
        //2.准备查询条件和参数
        List<Coupon> couponList = lambdaQuery()
                .eq(Coupon::getStatus, status)//条件：状态
                .eq(Coupon::getUserId, userId)//条件：用户id
                .lt(ObjectUtil.isNotNull(lastId), Coupon::getId, lastId)//条件（如果有）：小于lastId
                .select(Coupon::getId)//只查优惠券id
                .orderByDesc(Coupon::getId)//按id降序排列
                .last(" limit 10")//只查10条
                .list();
        if (ObjectUtil.isNull(couponList)) {
            return new ArrayList<>();
        }
        //3.提取id集合
        List<Long> longs = couponList.stream().map(Coupon::getId).collect(Collectors.toList());
        //4.根据ids查询优惠券并转换返回
        if(null == longs || longs.isEmpty()){
            return new ArrayList<>();
        }
        List<Coupon> coupons = baseMapper.selectBatchIds(longs);
        List<CouponInfoResDTO> couponInfoResDTOS = BeanUtil.copyToList(coupons, CouponInfoResDTO.class);
        return couponInfoResDTOS;
    }

    /**
     * 根据金额计算用户可用优惠券列表
     *
     * @param totalAmount 金额
     * @return
     */
    @Override
    public List<AvailableCouponsResDTO> getAvailable(BigDecimal totalAmount) {
        //获取当前用户
        Long userId = UserContext.currentUserId();
        //1.查询优惠券
        List<Coupon> couponList = lambdaQuery()
                .eq(Coupon::getUserId, userId)//用户id
                .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())//未使用
                .gt(Coupon::getValidityTime, LocalDate.now())//有效期大于当前时间
                .le(Coupon::getAmountCondition, totalAmount)//满减金额小于等于总金额
                .list();
        //1.1 非空判断
        if (ObjectUtil.isNull(couponList)) {
            return new ArrayList<>();
        }
        //2.组装数据计算优惠金额
        // 对筛选出的优惠券进行处理，计算每张优惠券的优惠金额，并过滤出符合条件的优惠券
        return couponList.stream()
                // 使用工具方法计算优惠金额
                .peek(coupon -> coupon.setDiscountAmount(CouponUtils.calDiscountAmount(coupon, totalAmount)))
                // 过滤优惠金额大于0且小于订单金额的优惠券
                .filter(coupon -> coupon.getDiscountAmount().compareTo(new BigDecimal(0)) > 0 && coupon.getDiscountAmount().compareTo(totalAmount) < 0)
                // 类型转换
                .map(coupon -> BeanUtil.toBean(coupon, AvailableCouponsResDTO.class))
                // 按优惠金额降序排序
                .sorted(Comparator.comparing(AvailableCouponsResDTO::getDiscountAmount).reversed())
                .collect(Collectors.toList());
    }

    /**
     * 使用优惠券并返回优惠金额
     *
     * @param couponUseReqDTO 使用优惠券参数
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CouponUseResDTO use(CouponUseReqDTO couponUseReqDTO) {
        //1.查询优惠券是否存在
        //获取用户id
        Long userId = UserContext.currentUserId();
        //根据优惠券id查询优惠券信息
        Coupon coupon = getById(couponUseReqDTO.getId());
        if (ObjectUtil.isNull(coupon)) {
            throw new LogicException("优惠券不存在！");
        }
        if (ObjectUtil.notEqual(coupon.getUserId(), userId)) {
            throw new LogicException("只允许核销自己的优惠券！");
        }
        //2.更新优惠券状态
        boolean update = lambdaUpdate()
                .eq(Coupon::getId, couponUseReqDTO.getId())// 优惠券id
                .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())// 状态为未使用
                .gt(Coupon::getValidityTime, LocalDate.now())// 有效期大于当前时间
                .le(Coupon::getAmountCondition, couponUseReqDTO.getTotalAmount())// 优惠金额小于等于总金额
                .set(Coupon::getOrdersId, couponUseReqDTO.getOrdersId())// 设置订单id
                .set(Coupon::getStatus, CouponStatusEnum.USED.getStatus())// 设置状态为已使用
                .set(Coupon::getUseTime, DateUtils.now())// 设置使用时间为当前时间
                .update();
        if (!update) {
            throw new LogicException("优惠券核销失败！");
        }
        //3.添加核销记录
        CouponWriteOff couponWriteOff = CouponWriteOff.builder()
                .id(IdUtils.getSnowflake().nextId())// id雪花算法
                .couponId(couponUseReqDTO.getId())//优惠券id
                .userId(userId.longValue())//用户id
                .ordersId(couponUseReqDTO.getOrdersId())//核销时使用的订单号
                .activityId(coupon.getActivityId())//活动id
                .writeOffTime(DateUtils.now())//核销时间
                .writeOffManName(coupon.getUserName())//核销人姓名
                .writeOffManPhone(coupon.getUserPhone())//核销人手机号
                .build();
        if (!couponWriteOffService.save(couponWriteOff)) {
            throw new LogicException("优惠券核销失败！");
        }
        //4.计算优惠金额返回
        BigDecimal bigDecimal = CouponUtils.calDiscountAmount(coupon, couponUseReqDTO.getTotalAmount());
        return CouponUseResDTO.builder().discountAmount(bigDecimal).build();
    }

    /**
     * 优惠券回退
     *
     * @param couponUseBackReqDTO 回退参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void useBack(CouponUseBackReqDTO couponUseBackReqDTO) {
        //1.查询优惠券信息：根据订单id和用户id查询优惠券
        Coupon coupon = lambdaQuery()
                .eq(Coupon::getOrdersId, couponUseBackReqDTO.getOrdersId())
                .eq(Coupon::getUserId, couponUseBackReqDTO.getUserId()).one();
        if(ObjectUtil.isNull(coupon)){
            return;//没有用到优惠券直接返回
        }
        //2.更新优惠券，判断是否过期执行不同的代码逻辑
        LambdaUpdateChainWrapper<Coupon> wrapper = lambdaUpdate().eq(Coupon::getId, coupon.getId());
        if (coupon.getValidityTime().isBefore(LocalDateTime.now())){
            //如果有效期在当前时间之前代表：过期：标记优惠券已作废
            wrapper.eq(Coupon::getStatus, CouponStatusEnum.VOIDED.getStatus());
        }else {
            //未过期：标记该优惠券未使用，清空订单id字段及使用时间字段
            wrapper.eq(Coupon::getStatus,CouponStatusEnum.NO_USE.getStatus())
                    .eq(Coupon::getOrdersId,null)
                    .eq(Coupon::getUseTime,null);
        }
        if (!wrapper.update()){
            log.info("修改优惠卷状态失败：优惠券id:{},用户id:{},活动id:{},有效期：{}", coupon.getId(), couponUseBackReqDTO.getUserId(), coupon.getActivityId(), coupon.getValidityTime());
            throw new LogicException("优惠券回退失败！");
        }
        //3.查询核销表并删除记录
        CouponWriteOff couponWriteOff = couponWriteOffService.lambdaQuery()
                .eq(CouponWriteOff::getCouponId, coupon.getId())
                .eq(CouponWriteOff::getActivityId, coupon.getActivityId())
                .eq(CouponWriteOff::getUserId, couponUseBackReqDTO.getUserId())
                .one();
        if (ObjectUtil.isNull(couponWriteOff)){
            log.info("优惠券核销记录表不存在！优惠券id:{},用户id:{},活动id:{}", coupon.getId(), couponUseBackReqDTO.getUserId(), coupon.getActivityId());
            throw new LogicException("优惠回退失败！");
        }
        //删除核销记录表
        couponWriteOffService.removeById(couponWriteOff.getId());
        //4.添加回退记录
        CouponUseBack couponUseBack = CouponUseBack.builder()
                .id(IdUtils.getSnowflake().nextId())//id：雪花算法
                .couponId(coupon.getId())//优惠券id
                .userId(couponUseBackReqDTO.getUserId())//用户id
                .useBackTime(DateUtils.now())//回退时间
                .writeOffTime(couponWriteOff.getWriteOffTime())//核销时间
                .build();
        if (!couponUseBackService.save(couponUseBack)){
            log.info("添加优惠券回退记录失败！优惠券id:{},用户id:{},活动id:{}", coupon.getId(), couponUseBackReqDTO.getUserId(), coupon.getActivityId());
            throw new LogicException("优惠回退失败！");
        }
    }

    /**
     * 处理过期优惠券
     */
    @Override
    public void processExpireCoupon() {
        // 使用Lambda表达式进行条件更新
        lambdaUpdate()
            // 设置优惠券状态为无效
            .set(Coupon::getStatus, CouponStatusEnum.INVALID.getStatus())
            // 条件：优惠券当前状态为未使用
            .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())
            // 条件：优惠券满足使用条件的日期小于等于当前日期
            .le(Coupon::getAmountCondition, DateUtils.now())
            // 执行更新操作
            .update();
    }

    @Override
    public void seizeCoupon(SeizeCouponReqDTO seizeCouponReqDTO) {
        //1.校验活动开始时间或结束
        //首先从缓存中查询活动
        ActivityInfoResDTO activityInfoResDTO= activityService.getActivityInfoByIdFromCache(seizeCouponReqDTO.getId());
        LocalDateTime now = DateUtils.now();
        if (activityService==null || activityInfoResDTO.getDistributeStartTime().isAfter(now)){
            throw new LogicException("活动未开始！");
        }
        if (activityInfoResDTO.getDistributeEndTime().isBefore(now)){
            throw new LogicException("活动已经结束！");
        }
        //2.抢券准备
        //key：抢券同步队列，资源库存，抢券列表
        //argv：抢券id，用户id
        int index= (int)(seizeCouponReqDTO.getId()%10);
        //同步队列key：QUEUE:COUPON:SEIZE:SYNC:{%s}
        String couponSeizeSyncRedisKey=String.format("QUEUE:%s:{%s}", RedisConstants.RedisKey.COUPON_SEIZE_SYNC_QUEUE_NAME, index);
        //资源库存key：COUPON:RESOURCE:STOCK:{%s}
        String resourceStockRedisKey=String.format(RedisConstants.RedisKey.COUPON_RESOURCE_STOCK,index);
        //抢券列表key：COUPON:SEIZE:LIST:%s_{%s}
        String couponSeizeListRedisKey=String.format(RedisConstants.RedisKey.COUPON_SEIZE_LIST,activityInfoResDTO.getId(),index);

        //3.执行lua脚本进行抢券结果
        Object execute = redisTemplate.execute(seizeCouponScript,//脚本对象
                Arrays.asList(couponSeizeSyncRedisKey, resourceStockRedisKey, couponSeizeListRedisKey),//抢券同步队列，资源库存，抢券列表
                seizeCouponReqDTO.getId(), UserContext.currentUserId());//抢券id，用户id

        //4.处理lus脚本结果，失败抛出异常，成功的正常返回
        if (execute==null){
            throw new LogicException("抢券失败！");
        }
        Long result = NumberUtil.parseLong(execute.toString());
        if (result>0){
            return;
        }
        if (result==-1){
            throw new LogicException("限领一张！");
        }
        if (result==-2 || result==-4){
            throw new LogicException("已抢光！");
        }
        throw new LogicException("抢券失败！");
    }
}
